Springboot+MyBatis

您所在的位置:网站首页 mybatis-plus 动态数据源 添加 Springboot+MyBatis

Springboot+MyBatis

2024-04-26 14:25| 来源: 网络整理| 查看: 265

Springboot+MyBatis-Plus实现多租户动态数据源模式

Spring DataSource 的工作原理

在说明动态切换数据源之前,我们需要先了解一下 spring 在单数据源情况下是如何工作的。我们先说一下什么是 DataSource? 有什么用呢?

请看 DataSource 接口定义:

package javax.sql; public interface DataSource extends CommonDataSource, Wrapper { Connection getConnection() throws SQLException; Connection getConnection(String username, String password) throws SQLException; }

聪明的你肯定一下就明白了,原来 DataSource 就是一个获取数据库 connection 的工厂类。然后我们还发现它的包名是 javax.sql,也就是说它是一个标准。常见的 C3P0、DBCP、Hikari、Druid 等等数据库连接池都实现了这个接口。

ok, 数据源我们现在弄明白了,那数据源是如何被 spring 使用的呢?以我们现在用的最广泛的 springboot 为例,我们在 application.properties 中配置了数据库连接信息后,mybatis,spring-data-jpa 等等 orm 框架就可以直接工作了,why?

其实原理很简单,我猜你也想到了。spring 在初始化系统的过程中读取 application.properties 中的数据库配置信息,然后实例化一个 DataSource bean 对象,mybatis、spring-data-jpa 等想要操作数据库时获取这个 DataSource 对象,然后调用其 getConnection () 方法获得数据库连接,然后操作数据库。

AbstractRoutingDataSource 工作原理

我们搞明白了 DataSource 工作原理,那么 AbstractRoutingDataSource 又是如何工作的呢?

我们先抛开 spring 的设计,一起思考一下。由上面的 DataSource 原理我们知道,一个 DataSource 代表一个数据库。那么我们要实现切换数据库,只要每次执行 sql 之前,从不同的数据源获得连接就可以了。换言之,我们需要实例化多个不同的数据源,然后每次使用的时候取不同的数据源来用。

ok,进入正题,看看 spring 是如何设计的。

AbstractRoutingDataSource 是 spring 提供的一个抽象类,为了看的清楚,我们先看一下唯一一个需要被实现的方法:

public abstract class AbstractRoutingDataSource extends AbstractDataSource implements InitializingBean { @Nullable protected abstract Object determineCurrentLookupKey(); }

这个方法没有参数,并且返回一个 Object 值,这个值使干嘛的呢?我们暂且放一放,继续往下看(真源码)。(为了大家看的清晰,我删掉了一些无关紧要的内容,保留了主要逻辑)

public abstract class AbstractRoutingDataSource extends AbstractDataSource implements InitializingBean { @Nullable private Map targetDataSources; @Nullable private Object defaultTargetDataSource; @Nullable private Map resolvedDataSources; @Nullable private DataSource resolvedDefaultDataSource; //设置需要切换的所有数据源(除了默认数据源) public void setTargetDataSources(Map targetDataSources) { this.targetDataSources = targetDataSources; } //默认数据源 public void setDefaultTargetDataSource(Object defaultTargetDataSource) { this.defaultTargetDataSource = defaultTargetDataSource; } public void afterPropertiesSet() { this.resolvedDataSources = new HashMap(this.targetDataSources.size()); this.targetDataSources.forEach((lookupKey, dataSource) -> { this.resolvedDataSources.put(lookupKey, dataSource); }); if (this.defaultTargetDataSource != null) { this.resolvedDefaultDataSource = defaultTargetDataSource; } } protected DataSource determineTargetDataSource() { Object lookupKey = this.determineCurrentLookupKey(); DataSource dataSource = (DataSource)this.resolvedDataSources.get(lookupKey); if (dataSource == null) { dataSource = this.resolvedDefaultDataSource; } return dataSource; } @Nullable protected abstract Object determineCurrentLookupKey(); }

接下来我们来模拟一下 AbstractRoutingDataSource 的使用过程,并说明上面代码的工作原理。

class Demo { public static void main(String args[]){ //初始化动态数据源 DemoDynamicDataSource dds = new DemoDynamicDataSource(); dds.setDefaultTargetDataSource(new DataSource()); HashMap targetDataSources = new HashMap(); targetDataSources.put("1",new DataSource()); targetDataSources.put("2",new DataSource()); dds.setTargetDataSources(targetDataSources); //spring会在bean初始化最后调用实现了InitializingBean接口bean的afterPropertiesSet() dds.afterPropertiesSet(); //使用动态数据源 DataSource datasource = dds.determineTargetDataSource(); datasource.getConnection().execute("select xx"); } }

初始化过程的核心在:afterPropertiesSet (); 代码很简单,将 defaultDataSource 存起来,将 TargetDataSources 转存到一个 map 里。

而使用的核心在:dds.determineTargetDataSource (), 我们看到,首先调用了我们需要实现的 determineCurrentLookupKey () 方法,然后通过获取到的 key 到上一步初始化的 targetDataSource 中取对应的 datasource (取不到就使用默认的 defaultDataSource),然后返回 datasource。

ok,我们现在明白了,原来我们可以通过 determineCurrentLookupKey () 方法的返回值来控制我们使用哪个数据源。

一、先实现动态数据源上下文模式代码,保证在多租户模式下,能自动根据租户Id切换数据源二、实现动态数据源添加和设置,并继承自AbstractRoutingDataSource类,实现其determineTargetDataSource和determineCurrentLookupKey方法三、实现动态数据源切面拦截,并根据租户Id实现数据源的动态切换四、实现动态数据源初始化,并将租户信息表中的数据库链接等查询出来一并初始化五、配置Mybatis六、租户表相关建表语句: 在默认数据库比如Test里建立租户表tenant_info,每个租户在mysql加一个用户,比如user123,user456, 每个用户建立一个同名的数据库 

GRANT ALL PRIVILEGES ON user123.* to 'user123'@'%';

CREATE TABLE `tenant_info` ( `id` bigint(20) NOT NULL AUTO_INCREMENT, `TENANT_ID` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL COMMENT '租户id', `TENANT_NAME` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL COMMENT '租户名称', `DATASOURCE_URL` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL COMMENT '数据源url', `DATASOURCE_USERNAME` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL COMMENT '数据源用户名', `DATASOURCE_PASSWORD` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL COMMENT '数据源密码', `DATASOURCE_DRIVER` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL COMMENT '数据源驱动', `SYSTEM_ACCOUNT` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL COMMENT '系统账号', `SYSTEM_PASSWORD` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL COMMENT '账号密码', `SYSTEM_PROJECT` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL COMMENT '系统PROJECT', `STATUS` tinyint(1) DEFAULT NULL COMMENT '是否启用(1是0否)', `CREATE_TIME` datetime DEFAULT NULL COMMENT '创建时间', `UPDATE_TIME` datetime DEFAULT NULL COMMENT '更新时间', PRIMARY KEY (`id`) USING BTREE ) ENGINE=InnoDB AUTO_INCREMENT=13 DEFAULT CHARSET=utf8 ROW_FORMAT=DYNAMIC; SET FOREIGN_KEY_CHECKS = 1; CREATE TABLE `t_user` ( `id` int NOT NULL AUTO_INCREMENT, `Operator` varchar(255) DEFAULT NULL, `Status` varchar(255) DEFAULT NULL, `Id_Card` varchar(255) DEFAULT NULL, `Sex` varchar(255) DEFAULT NULL, `Dept_Id` varchar(255) DEFAULT NULL, `Birthday` datetime DEFAULT NULL, `Update_Time` datetime DEFAULT NULL, `Is_Admin` varchar(255) DEFAULT NULL, `User_Name` varchar(255) DEFAULT NULL, `E_Mail` varchar(255) DEFAULT NULL, `Real_Name` varchar(255) DEFAULT NULL, `Tel_Phone` varchar(255) DEFAULT NULL, `Entry_Time` datetime DEFAULT NULL, `Login_Time` datetime DEFAULT NULL, `Expire_Time` datetime DEFAULT NULL, `Position_Id` varchar(255) DEFAULT NULL, `Create_Time` datetime DEFAULT NULL, `Resignation_Time` datetime DEFAULT NULL, `Expire_Status` datetime DEFAULT NULL, `Password` varchar(255) DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=6

  

实现源码地址https://github.com/achievejia/springboot_saas  (针对mybatis plus 3.2,最新的3.5.2不适用,要修改代码)



【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3